<?php

#<editor-fold defaultstate="collapsed" desc="CCore::Using">
CCore::Using('socket.inc.constants');
CCore::Using('socket.inc.abstract');
CCore::Using('socket.pop3.config');
CCore::Using('base.developments');
CCore::Using('base.utils');
#</editor-fold>

class CCoreSocketPop3 extends ACoreSocket
{
	#<editor-fold defaultstate="collapsed" desc="private">
	/**
	 * @var	string
	 */
	private $_sApopBanner;
	#</editor-fold>
	
	/**
	 * @param	CCoreSocketPop3Config	$oPop3Config
	 * @param	array					$aFollowers = array()
	 * @return	CCoreSocketPop3
	 */
	public function __construct(CCoreSocketPop3Config $oPop3Config, $aFollowers = array())
	{
		CCore::CheckTypes(array('$oPop3Config', '$aFollowers'));

		$oDevelopments = new CCoreBaseDevelopments('POP3', 'POP3[Error]', 'POP3[Warning]', 'POP3[Notice]');
		$oDevelopments->AddArray($aFollowers);
		
		parent::__construct($oPop3Config, $oDevelopments);
		$this->_sApopBanner = '';
	}

	/**
	 * @return	void
	 */
	public function __destruct()
	{
		$this->LogoutAndDisconnect();
	}

	/**
	 * @return	bool
	 */
	public function Connect()
	{
		$iPort = $this->oConfig->GetValue('Port', 'int');
		return parent::Connect(995 === $iPort) && $this->_isSuccess() && $this->_parseApopBanner($this->GetLastBuffer());
	}
	
	/**
	 * @return	bool
	 */
	public function Login()
	{
		$sLogin = $this->oConfig->GetValue('Login', 'string');
		$sPassword = $this->oConfig->GetValue('Password', 'string');
		
		if (strlen($this->_sApopBanner) > 0)
		{
			return $this->_executeCommand('APOP '.$sLogin.' '.md5($this->_sApopBanner.$sPassword));
		}
		else
		{
			return $this->_executeCommand('USER '.$sLogin) && $this->_executeCommand('PASS '.$sPassword);
		}
	}

	/**
	 * @return	bool
	 */
	public function ConnectAndLogin()
	{
		return $this->Connect() && $this->Login();
	}

	/**
	 * @return	bool
	 */
	public function Logout()
	{
		return $this->_executeCommand('QUIT');
	}

	/**
	 * @return	bool
	 */
	public function LogoutAndDisconnect()
	{
		return $this->Logout() && $this->Disconnect();
	}

	/**
	 * @return	bool
	 */
	public function Noop()
	{
		return $this->_executeCommand('NOOP');
	}

	/**
	 * @return	bool
	 */
	public function Rset()
	{
		return $this->_executeCommand('RSET');
	}

	/**
	 * @param	int		$iIndex
	 * @return	bool
	 */
	public function DeleteMessage($iIndex)
	{
		CCore::CheckTypes(array('$iIndex'));
		
		return $this->_executeCommand('DELE '.$iIndex);
	}

	/**
	 * @param	int		$iIndex
	 * @param	int		$iLines = 0
	 * @return	string | bool
	 */
	public function GetTop($iIndex, $iLines = 0)
	{
		CCore::CheckTypes(array('$iIndex', '$iLines'));

		return $this->_getRawLines('TOP '.$iIndex.' '.$iLines);
	}

	/**
	 * @param	int		$iIndex
	 * @return	string | bool
	 */
	public function GetMessage($iIndex)
	{
		CCore::CheckTypes(array('$iIndex'));

		return $this->_getRawLines('RETR '.$iIndex);
	}

	/**
	 * @return	array | bool
	 */
	public function Stat()
	{
		if (!$this->_executeCommand('STAT'))
		{
			return false;
		}

		$aMatch = array();
		if (preg_match('/\+OK ([\d]+) ([\d]+)/', $this->GetLastBuffer(), $aMatch)
				&& isset($aMatch[1], $aMatch[2]))
		{
			return array(
				'count' => (int) $aMatch[1],
				'size' => (int) $aMatch[2]
			);
		}
		return false;
	}

	/**
	 * @param	int		$niIndex = null
	 * @return	array | bool
	 */
	public function Uidl($niIndex = null)
	{
		CCore::CheckTypes(array('$niIndex'));
		return $this->_getIndexLines('UIDL', $niIndex, 'string');
	}

	/**
	 * @param	int		$niIndex = null
	 * @return	array | bool
	 */
	public function Sizes($niIndex = null)
	{
		CCore::CheckTypes(array('$niIndex'));
		return $this->_getIndexLines('LIST', $niIndex, 'integer');
	}
	
	/**
	 * @param	string	$sCommand
	 * @param	int		$niIndex = null
	 * @return	array | bool
	 */
	private function _getIndexLines($sCommand, $niIndex = null, $sValueType = 'string')
	{
		CCore::CheckTypes(array('$sCommand', '$niIndex', '$sValueType'));

		if (null !== $niIndex && $niIndex > 0)
		{
			if ($this->_executeCommand($sCommand.' '.$niIndex))
			{
				$aMatch = array();
				if (preg_match('/^\+OK ([\d]+) (.+)/', $this->GetLastBuffer(), $aMatch)
						&& isset($aMatch[1], $aMatch[2]))
				{
					$aMatch[2] = trim($aMatch[2]);
					if (settype($aMatch[2], $sValueType))
					{
						return array((int) $aMatch[1] => $aMatch[2]);
					}
				}
			}
		}
		else
		{
			$aLines = $this->_getRawLines($sCommand, true);
			if (is_array($aLines))
			{
				$aReturn = array();
				foreach ($aLines as $sLine)
				{
					$aMatch = array();
					if (preg_match('/^([\d]+) (.+)/', $sLine, $aMatch)
							&& isset($aMatch[1], $aMatch[2]))
					{
						$aMatch[2] = trim($aMatch[2]);
						if (settype($aMatch[2], $sValueType))
						{
							$aReturn[(int) $aMatch[1]] = $aMatch[2];
						}
					}
					unset($aMatch);
				}
				return $aReturn;
			}
		}
		
		return false;
	}
	
	/**
	 * @param	string	$sCommand
	 * @return	string | array | bool
	 */
	private function _getRawLines($sCommand, $bAsArray = false)
	{
		CCore::CheckTypes(array('$sCommand', '$bAsArray'));

		if ($this->_executeCommand($sCommand))
		{
			$aLines = array();
			while (true)
			{
				$sLine = $this->getLine();
				if (false === $sLine)
				{
					return false;
				}
				else if (substr($sLine, 0, 3) != '.'.C_SOCKET_CRLF)
				{
					$aLines[] = $sLine;
				}
				else
				{
					break;
				}
			}

			return ($bAsArray) ? $aLines : implode('', $aLines);
		}

		return false;
	}

	/**
	 * @param	string	$sCommand
	 * @return	bool
	 */
	private function _executeCommand($sCommand)
	{
		CCore::CheckTypes(array('$sCommand'));
		
		if (!$this->IsConnected())
		{
			return false;
		}

		return $this->putLine($sCommand) && $this->_isSuccess();
	}

	/**
	 * @return	bool
	 */
	private function _isSuccess()
	{
		$bResult = false;

		$sLine = $this->getLine();
		if (false === $sLine)
		{
			$this->oDevelopments->Write('false response.', CCoreBaseDevelopments::WARNING);
			$this->Disconnect();
		}
		else
		{
			if (substr($sLine, 0, 3) === '+OK')
			{
				$bResult = true;
			}
			else
			{
				$bResult = false;
				$this->oDevelopments->Write('response not success ('.
					CCoreBaseUtils::ShowCRLF($sLine).').', CCoreBaseDevelopments::WARNING);
				$this->Disconnect();
			}
		}

		return $bResult;
	}

	/**
	 * @param	string	$sLoginText
	 * @return	bool
	 */
	private function _parseApopBanner($sLoginText)
	{
		CCore::CheckTypes(array('$sLoginText'));
		
		$bOutside = true;
		$sPopBanner = '';
		for ($i = 0, $iLen = strlen($sLoginText); $i < $iLen; $i++)
		{
			$sDigit = substr($sLoginText, $i, 1);
			if ($sDigit != '')
			{
				if (!$bOutside && $sDigit != '<' && $sDigit != '>')
				{
					$sPopBanner .= $sDigit;
					continue;
				}
				if ($sDigit == '<')
				{
					$bOutside = false;
				}
				else if ($sDigit == '>')
				{
					$bOutside = true;
				}
			}
		}

		$sPopBanner = trim($sPopBanner);
		if (strlen($sPopBanner) != 0)
		{
			$this->_sApopBanner = '<'. $sPopBanner .'>';
		}

		return true;
	}
}